package edu.kit.csl.cratylus.ui;

import java.io.IOException;
import java.util.Iterator;
import java.util.List;

import edu.kit.csl.cratylus.datatypes.BadParameterValueException;
import edu.kit.csl.cratylus.datatypes.Candidate;
import edu.kit.csl.cratylus.datatypes.Corpus;
import edu.kit.csl.cratylus.datatypes.Vocabulary;
import edu.kit.csl.cratylus.datatypes.Word;
import edu.kit.csl.cratylus.extraction.AligningCorpusUpdater;
import edu.kit.csl.cratylus.extraction.CorpusUpdater;
import edu.kit.csl.cratylus.extraction.DropBlankWritingSystem;
import edu.kit.csl.cratylus.extraction.ElkiSplitter;
import edu.kit.csl.cratylus.extraction.FrequencyMerger;
import edu.kit.csl.cratylus.extraction.MSAMerger;
import edu.kit.csl.cratylus.extraction.MappingWritingSystem;
import edu.kit.csl.cratylus.extraction.MarxSplitter;
import edu.kit.csl.cratylus.extraction.Merger;
import edu.kit.csl.cratylus.extraction.Planner;
import edu.kit.csl.cratylus.extraction.RandomMerger;
import edu.kit.csl.cratylus.extraction.RelatedWordWritingSystem;
import edu.kit.csl.cratylus.extraction.ReplacingCorpusUpdater;
import edu.kit.csl.cratylus.extraction.SartreSplitter;
import edu.kit.csl.cratylus.extraction.SourceWordFrequencyPlanner;
import edu.kit.csl.cratylus.extraction.Splitter;
import edu.kit.csl.cratylus.extraction.SrilmMerger;
import edu.kit.csl.cratylus.extraction.WritingSystem;
import edu.kit.csl.cratylus.io.CorpusDumper;
import edu.kit.csl.cratylus.io.CorpusFactory;
import edu.kit.csl.cratylus.io.GizaCorpusDumper;
import edu.kit.csl.cratylus.io.LazyCorpusFactory;
import edu.kit.csl.cratylus.io.Logger;
import edu.kit.csl.cratylus.io.PisaCorpusDumper;
import edu.kit.csl.cratylus.io.PisaCorpusFactory;
import edu.kit.csl.cratylus.io.PlainCorpusDumper;
import edu.kit.csl.cratylus.io.VocabularyDumper;

/**
 * This class is the main class. It contains the main() method which starts the
 * main program work flow (load resources, extract data, output results).
 * 
 * @see #main(String[])
 */
public class Main {
	
	private static Corpus corpus;
	private static WritingSystem ws;

	/**
	 * Main runner method. Initializes the configuration singleton and 
	 * starts the main extraction loop, utilizing implementations of the four
	 * logical main modules planner, splitter, merger, and updater.
	 * 
	 * @param args Use -help to display command line arguments
	 * @see Planner
	 * @see Splitter
	 * @see Merger
	 * @see Updater
	 */
	public static void main(String[] args) {
		Logger log = Logger.getSingleton();
		// Load configuration
		Configuration conf = Configuration.getSingleton();
		if (args.length == 1 && args[0].equals("-help")) {
			conf.printHelp();
			return;
		}
		conf.processCommandLineArguments(args);
		if (conf.getString("alignmentFile").equals("")) {
			log.fatalError("Parameter alignmentFile must be set.");
		}
		if (log.isFatalErrorRegistered()) {
			log.fatalError("Fatal errors occurred. Use -help to show "
					+ "information about each available parameter");
			return;
		}
		log.notice("Configuration loaded.");
		CorpusFactory factory = createCorpusFactory();
		corpus = factory.createCorpus();
		log.notice("Corpus loaded with " + corpus.size() + " sentence pairs.");
		if (log.isFatalErrorRegistered()) {
			log.fatalError("Fatal errors occurred while loading the alignment "
					+ "file. Doublecheck the parameter alignmentFile and "
					+ "alignmentLoader and rerun.");
			return;
		}
		try {
			ws = createWritingSystem();
			startExtraction();
			writeToFileSystem();
		} catch (BadParameterValueException e) {
			Logger.getSingleton().fatalError("A bad parameter value aborted "
				+ "the extraction process: " + e.getMessage());
		}
	}
	
	/*
	 * Start the extraction process. This is the main extraction loop and shows
	 * the cooperation between planner, splitter, merger, and updater.
	 */
	private static void startExtraction() throws BadParameterValueException {
		Planner planner = createPlanner();
		Splitter splitter = createSplitter();
		Merger merger = createMerger();
		CorpusUpdater updater = createUpdater();
		Vocabulary trgtVoc = Vocabulary.getSingleton(Vocabulary.TRGT_VOC);
		if (Logger.getSingleton().isFatalErrorRegistered()) {
			Logger.getSingleton().fatalError("Fatal errors occured while "
					+ "initializing the extraction environment. Please double"
					+ "check the parameters planner, splitter, merger and "
					+ "corpusUpdater and rerun.");
			return;
		}
		Logger.getSingleton().notice("Start extraction.");
		for (List<Candidate> candidates : planner) {
			// Filter empty words (could ie. result from AligningCorpusUpdater)
			Iterator<Candidate> i = candidates.iterator();
			while (i.hasNext()) {
			    if (i.next().getWord().isEmpty()) {
			    	i.remove();
			    }
			}
			if (candidates.isEmpty()) {
				continue;
			}
			List<List<Candidate>> subsets = splitter.split(candidates);
			for (List<Candidate> subset : subsets) {
				Word trgtWord = merger.merge(subset);
				if (!trgtWord.isNoise()) {
					trgtVoc.put(ws.convert(trgtWord), trgtWord);
				} else {
					Logger.getSingleton().debug("Noise word: "
							+ trgtWord.getContent());
				}
				updater.updateCorpus(trgtWord, subset);
			}
		}
	}
	
	/*
	 * Initiates *Dump classes to dump corpus and/or the pronunciation
	 * dictionary to the file system, according to the outputFormat
	 * and the outputVocabulary parameter.
	 */
	private static void writeToFileSystem() throws BadParameterValueException {
		Configuration config = Configuration.getSingleton();
		int format = config.getInteger("outputFormat");
		String prefix = config.getString("outputPrefix");
		CorpusDumper dumper;
		try {
			if ((format & 1) > 0) {
				dumper = new PlainCorpusDumper();
				dumper.dumpToFileSystem(corpus, ws, prefix + ".plain");
			}
			if ((format & 2) > 0) {
				dumper = new GizaCorpusDumper();
				dumper.dumpToFileSystem(corpus, ws, prefix + ".giza");
			}
			if ((format & 4) > 0) {
				dumper = new PisaCorpusDumper();
				dumper.dumpToFileSystem(corpus, ws, prefix + ".pisa");
			}
			if (config.getInteger("outputVocabulary") > 0) {
				VocabularyDumper.dumpToFileSystem(Vocabulary.getSingleton(
						Vocabulary.TRGT_VOC), ws, prefix + ".dict");
			}
		} catch (IOException e) {
			throw new BadParameterValueException("I/O exception occured while "
					+ "writing to the file system: " + e.getMessage());
		}
	}
	
	/*
	 * Factory method for creating a writing system (GoF design pattern) 
	 * according to the parameter writingSystem.
	 */
	private static WritingSystem createWritingSystem()
			 throws BadParameterValueException {
		String l = Configuration.getSingleton().getString("writingSystem");
		if (l.equals("p2g")) {
			return new MappingWritingSystem();
		} else if (l.equals("dropBlanks")) {
			return new DropBlankWritingSystem();
		} else if (l.equals("source")) {
			return new RelatedWordWritingSystem();
		}
		Logger.getSingleton().fatalError("Writing system " + l
				+ " is not implemented. Fall back to dropBlanks writing "
				+ " system.");
		return new DropBlankWritingSystem();
	}
	
	/*
	 * Factory method for creating a corpus factory (GoF design pattern) 
	 * according to the parameter alignmentLoader.
	 */
	private static CorpusFactory createCorpusFactory() {
		String l = Configuration.getSingleton().getString("alignmentLoader");
		if (l.equals("pisa")) {
			return new PisaCorpusFactory();
		}
		Logger.getSingleton().fatalError("Alignment loader " + l
				+ " is not implemented.");
		return new LazyCorpusFactory();
	}
	
	/*
	 * Factory method for creating a planner (GoF design pattern) 
	 * according to the parameter planner.
	 */
	private static Planner createPlanner() {
		String l = Configuration.getSingleton().getString("planner");
		if (l.equals("sourceWordFrequency")) {
			return new SourceWordFrequencyPlanner(corpus);
		}
		Logger.getSingleton().fatalError("Planner " + l
				+ " is not implemented.");
		return null;
	}
	
	/*
	 * Factory method for creating a splitter (GoF design pattern) 
	 * according to the parameter splitter.
	 */
	private static Splitter createSplitter() 
			throws BadParameterValueException {
		String l = Configuration.getSingleton().getString("splitter");
		if (l.equals("marx")) {
			return new MarxSplitter();
		} else if (l.equals("sartre")) {
			return new SartreSplitter();
		} else if (l.equals("elki")) {
			return new ElkiSplitter();
		}
		Logger.getSingleton().fatalError("Splitter " + l
				+ " is not implemented.");
		return null;
	}
	
	/*
	 * Factory method for creating a merger (GoF design pattern) 
	 * according to the parameter merger.
	 */
	private static Merger createMerger() throws BadParameterValueException {
		String l = Configuration.getSingleton().getString("merger");
		if (l.equals("random")) {
			return new RandomMerger();
		} else if (l.equals("frequency")) {
			return new FrequencyMerger();
		} else if (l.equals("srilm")) {
			return new SrilmMerger();
		} else if (l.equals("msa")) {
			return new MSAMerger();
		}
		Logger.getSingleton().fatalError("Merger " + l
				+ " is not implemented.");
		return null;
	}
	
	/*
	 * Factory method for creating a corpus updater (GoF design pattern) 
	 * according to the parameter corpusUpdater.
	 */
	private static CorpusUpdater createUpdater() {
		String l = Configuration.getSingleton().getString("corpusUpdater");
		if (l.equals("replace")) {
			return new ReplacingCorpusUpdater();
		} else if (l.equals("align")) {
			return new AligningCorpusUpdater();
		}
		Logger.getSingleton().fatalError("Corpus updater " + l
				+ " is not implemented.");
		return null;
	}
}
